tool
extends ArrayMesh

class_name BeveledCubeMesh

export(Vector3) var offset: Vector3 = Vector3(0, 0, 0) setget set_offset
export(Vector3) var size: Vector3 = Vector3(2, 2, 2) setget set_size
export(float) var bevel: float = 0.0 setget set_bevel
export(float, 0.0, 1.0, 0.05) var bevel_normal_smoothness: float = 1.0 setget set_bevel_normal_smoothness

class MeshBuilder:
	var offset: Vector3
	var size: Vector3
	var bevel: float
	var bevel_normal_smoothness: float

	var vertices: PoolVector3Array = PoolVector3Array()
	var normals: PoolVector3Array = PoolVector3Array()
	var indices: PoolIntArray = PoolIntArray()

	var p: PoolVector3Array = PoolVector3Array()

	func _add_cube_face(NX: int, NY: int, NZ: int):
		vertices.push_back(Vector3(p[0].x+(0.0 if NX else bevel), \
			p[0].y+(0.0 if NY else bevel), \
			p[0].z+(0.0 if NZ else bevel)))
		vertices.push_back(Vector3(p[NZ].x+(0.0 if NX else -bevel if NZ else bevel), \
			p[NX].y+(0.0 if NY else -bevel if NX else bevel), \
			p[NY].z+(0.0 if NZ else -bevel if NY else bevel)))
		vertices.push_back(Vector3(p[NY].x+(0.0 if NX else -bevel if NY else bevel), \
			p[NZ].y+(0.0 if NY else -bevel if NZ else bevel), \
			p[NX].z+(0.0 if NZ else -bevel if NX else bevel)))
		vertices.push_back(Vector3(p[1-NX].x-(0.0 if NX else bevel), \
			p[1-NY].y-(0.0 if NY else bevel), \
			p[1-NZ].z-(0.0 if NZ else bevel)))
		for _i in range(4):
			normals.push_back(Vector3(-NX, -NY, -NZ))

		vertices.push_back(Vector3(p[1].x-(0.0 if NX else bevel), \
			p[1].y-(0.0 if NY else bevel), \
			p[1].z-(0.0 if NZ else bevel)))
		vertices.push_back(Vector3(p[1-NZ].x-(0.0 if NX else -bevel if NZ else bevel), \
			p[1-NX].y-(0.0 if NY else -bevel if NX else bevel), \
			p[1-NY].z-(0.0 if NZ else -bevel if NY else bevel)))
		vertices.push_back(Vector3(p[1-NY].x-(0.0 if NX else -bevel if NY else bevel), \
			p[1-NZ].y-(0.0 if NY else -bevel if NZ else bevel), \
			p[1-NX].z-(0.0 if NZ else -bevel if NX else bevel)))
		vertices.push_back(Vector3(p[NX].x+(0.0 if NX else bevel), \
			p[NY].y+(0.0 if NY else bevel), \
			p[NZ].z+(0.0 if NZ else bevel)))
		for _i in range(4):
			normals.push_back(Vector3(NX, NY, NZ))

	func _add_rect(I: int, J: int, K: int, L: int):
		_add_tri(I, J, K)
		_add_tri(J, L, K)

	func _add_rect_2(I: int):
		_add_rect(I, I+1, I+2, I+3)

	func _add_tri(I: int, J: int, K: int):
		indices.push_back(I)
		indices.push_back(J)
		indices.push_back(K)
		indices.push_back(I^4)
		indices.push_back(K^4)
		indices.push_back(J^4)

	func _init(p_offset: Vector3, p_size: Vector3, p_bevel: float, p_bevel_normal_smoothness: float):
		offset = p_offset
		size = p_size
		bevel = p_bevel
		bevel_normal_smoothness = p_bevel_normal_smoothness

		p.push_back(offset + size * -0.5)
		p.push_back(offset + size * 0.5)

		_add_cube_face(1, 0, 0)
		_add_cube_face(0, 1, 0)
		_add_cube_face(0, 0, 1)

		_add_rect_2(0)
		_add_rect_2(8)
		_add_rect_2(16)

		if bevel > 1e-6:
			_add_rect(0, 2, 8, 9)
			_add_rect(8, 10, 16, 17)
			_add_rect(16, 18, 0, 1)
			_add_rect(10, 11, 7, 5)
			_add_rect(18, 19, 15, 13)
			_add_rect(2, 3, 23, 21)
			_add_tri(0, 8, 16)
			_add_tri(2, 23, 9)
			_add_tri(10, 7, 17)
			_add_tri(18, 15, 1)

func _create_mesh():
	var mat: Material = null
	if get_surface_count() > 0:
		# backup the original material
		mat = surface_get_material(0)

		# remove all faces
		while get_surface_count() > 0:
			surface_remove(0)

	var b: MeshBuilder = MeshBuilder.new(offset, size, bevel, bevel_normal_smoothness)
	var arrays: Array = []
	arrays.resize(ARRAY_MAX)
	arrays[ARRAY_VERTEX] = b.vertices
	arrays[ARRAY_NORMAL] = b.normals
	arrays[ARRAY_INDEX] = b.indices

	add_surface_from_arrays(PRIMITIVE_TRIANGLES, arrays)
	surface_set_material(0, mat)

	emit_signal("changed")

func _init():
	_create_mesh()

func set_offset(p_offset: Vector3):
	if offset != p_offset:
		offset = p_offset
		_create_mesh()

func set_size(p_size: Vector3):
	if size != p_size:
		size = p_size
		_create_mesh()

func set_bevel(p_bevel: float):
	p_bevel = max(p_bevel, 0.0)
	if bevel != p_bevel:
		bevel = p_bevel
		_create_mesh()

func set_bevel_normal_smoothness(p_smoothness: float):
	p_smoothness = clamp(p_smoothness, 0.0, 1.0)
	if bevel_normal_smoothness != p_smoothness:
		bevel_normal_smoothness = p_smoothness
		_create_mesh()
